﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using BrainTechLLC.ThreadSafeObjects;
using System.Collections.Specialized;
using AltSerialize;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC
{
    [Serializable]
    [DataContract]
    public class WorldMap : ICanSaveAndRestore, INotifyCollectionChanged
    {
        [NonSerialized, XmlIgnore]
        public static WorldMap Instance = new WorldMap();

        [DataMember]
        public SparseArrayCustom<GameLocation> World = new SparseArrayCustom<GameLocation>();

        public void SetupNotification()
        {
#if NO_SILVERLIGHT
            World.CollectionChanged += new NotifyCollectionChangedEventHandler(World_CollectionChanged);
#else
            World.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgsEx<GameLocation>>(World_CollectionChanged);
#endif

            World.AllTerminatingItemsList.ForEach((kvp) =>
                {
#if NO_SILVERLIGHT
                    kvp.Value.CollectionChanged += new NotifyCollectionChangedEventHandler(Value_CollectionChanged);
#else
                    kvp.Value.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgsEx<IdentifierAndValue>>(Value_CollectionChanged);
#endif
                    kvp.Value.RegisterChildrenForPropertyChangeEvents();
                });
        }

        void Value_CollectionChanged(object sender, NotifyCollectionChangedEventArgsEx<IdentifierAndValue> e)
        {
            // TODO - not working for silverlight
#if NO_SILVERLIGHT
            if (CollectionChanged != null)
                CollectionChanged(sender, e);
#endif
        }

        void World_CollectionChanged(object sender, NotifyCollectionChangedEventArgsEx<GameLocation> e)
        {
            if (CollectionChanged != null)
                CollectionChanged(sender, e);
        }

#if NO_SILVERLIGHT
        void Value_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
                CollectionChanged(sender, e);
        }

        void World_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
                CollectionChanged(sender, e);
        }
#endif

        [NonSerialized, XmlIgnore]
        public ThreadSafeLookup<long, GameLocation> GameLocationLookupByID = new ThreadSafeLookup<long, GameLocation>();

        [NonSerialized, XmlIgnore]
        public ThreadSafeLookup<long, GameLocation> FindLocationByThingID = new ThreadSafeLookup<long, GameLocation>();

        [NonSerialized, XmlIgnore]
        public ThreadSafeListValues<long> AllGameThingsInWorld = new ThreadSafeListValues<long>();

        [NonSerialized, XmlIgnore]
        public ThreadSafeList<IGameThing> AllGameThingsCached; // TODO

        public void SwapObject(IGameThing oldObject, IGameThing newObject)
        {

        }

        public void PerformPostRestoreSetup()
        {
            FindLocationByThingID = new ThreadSafeLookup<long, GameLocation>();
            GameLocationLookupByID = new ThreadSafeLookup<long, GameLocation>();
            AllGameThingsInWorld = new ThreadSafeListValues<long>();
            var v = World.AllTerminatingItemsList;
            v.ForEach((o) => GameLocationLookupByID.AddOrSet(o.Value.AssociatedID, o.Value));

            v.ForEach((o) =>
            {
                o.Value.Register();

                if (o.Value.Things != null)
                {
                    List<long> ids = o.Value.Things.AllIDs;
                    for (int n = 0; n < ids.Count; n++)
                    {
                        FindLocationByThingID.Lookup.Add(ids[n], o.Value);
                        AllGameThingsInWorld.Add(ids[n]);
                    }
                }
            });
        }

        [NonSerialized, XmlIgnore]
        protected const string SavePath = "C:\\World.dat";

        public void Save(List<object> arguments)
        {
            if (_lastSaved < _lastChanged)
            {
#if NO_SILVERLIGHT
                Serializer s = new Serializer();

                foreach (var type in IncomingMessageHandler.KnownTypesArray)
                    s.CacheObject(type);

                byte[] bytes = s.Serialize(this);

                File.WriteAllBytes(SavePath, bytes.Compress());
#endif
                _lastSaved = DateTime.Now;
            }

            Everything.SaveEverythingList();
            Everything.SaveAllGameThings();
        }

        public bool Restore<T>(List<object> arguments, out T restored) where T : class
        {
            restored = null;
#if NO_SILVERLIGHT
            WorldMap map;
#endif
            MarkRestored();
#if NO_SILVERLIGHT
            if (File.Exists(SavePath))
            {
                byte[] bytes = File.ReadAllBytes(SavePath);
                Serializer s = new Serializer();
                foreach (var type in IncomingMessageHandler.KnownTypesArray)
                    s.CacheObject(type);
                map = (WorldMap)s.Deserialize(bytes.Decompress(), typeof(WorldMap));
                //map = bytes.Decompress(); // DeserializeFromCompressedBinary() as WorldMap;
                restored = map as T;
                map.PerformPostRestoreSetup();
                Everything.LoadAllGameThings();
                return true;
            }
            else
            {
                map = new WorldMap();
                restored = map as T;
                return false;
            }
#else
            return false;
#endif
        }


        public long FindLocation(LocationInt location)
        {
            GameLocation loc = World.FindItem(location.X, location.Y, location.Z);

            if (loc == null)
                return 0;

            return loc.AssociatedID;
        }


        public GameLocation ToLocation(long id, LocationInt location)
        {
            long loc = FindLocation(location);
            return ToLocation(id, loc);
        }

        public GameLocation ToLocation(long id, long location)
        {
            GameLocation gameLocation = GameLocationLookupByID[location];
            return ToLocation(id, gameLocation);
        }

        public GameLocation ToLocation(long id, GameLocation gameLocation)
        {
            GameLocation oldGameLocation = FindLocationByThingID[id];

            if (gameLocation == null)
                return oldGameLocation;

            if (oldGameLocation != null)
            {
                oldGameLocation.Things.Remove(id);
                FindLocationByThingID.Remove(id);
                AllGameThingsInWorld.Remove(id);
            }

            gameLocation.Things.Add(id);
            FindLocationByThingID.Add(id, gameLocation);
            AllGameThingsInWorld.Add(id);

            if (oldGameLocation == null)
            {
                AllGameThingsCached = null;
            }

            MarkChanged();
            return oldGameLocation;
        }

        public void FromLocation(long id, long location)
        {
            GameLocation gameLocation = GameLocationLookupByID[id];
            gameLocation.Things.Remove(id);
            FindLocationByThingID.Remove(id);
            AllGameThingsInWorld.Remove(id);
            AllGameThingsCached = null;
            MarkChanged();
        }

        public List<T> FindAllThingsOfType<T>() where T : class, IGameThing
        {
            if (AllGameThingsCached == null)
            {
                lock (this)
                {
                    if (AllGameThingsCached == null)
                    {
                        ThreadSafeList<IGameThing> temp = new ThreadSafeList<IGameThing>();
                        for (int n = 0; n < AllGameThingsInWorld.Count; n++)
                        {
                            IGameThing thing = (IGameThing)AllGameThingsInWorld[n].Find();

                            if (thing != null)
                                temp.Add(thing);
                        }
                        AllGameThingsCached = temp;
                    }
                }
            }
            List<T> newCollection = new List<T>();

            foreach (IGameThing thing in AllGameThingsCached)
            {
                if (typeof(T).IsAssignableFrom(thing.GetType()))
                {
                    newCollection.Add(thing as T);
                }
            }

            return newCollection;
        }

        public List<IGameThing> FindAllThingsOfType(Type t)
        {
            if (AllGameThingsCached == null)
            {
                lock (this)
                {
                    if (AllGameThingsCached == null)
                    {
                        ThreadSafeList<IGameThing> temp = new ThreadSafeList<IGameThing>();
                        for (int n = 0; n < AllGameThingsInWorld.Count; n++)
                        {
                            IGameThing thing = (IGameThing)AllGameThingsInWorld[n].Find();

                            if (thing != null)
                                temp.Add(thing);
                        }
                        AllGameThingsCached = temp;
                    }
                }
            }
            List<IGameThing> newCollection = new List<IGameThing>();

            foreach (IGameThing thing in AllGameThingsCached)
            {
                if (t.IsAssignableFrom(thing.GetType()))
                {
                    newCollection.Add(thing);
                }
            }

            return newCollection;
        }

        public GameLocation FindLocationOfThingID(long id)
        {
            GameLocation loc = FindLocationByThingID[id];

            if (loc == null)
                return null;

            return loc;
        }

        #region INotifyCollectionChanged Members

#if NO_SILVERLIGHT
        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;
#else
        [field: NonSerialized]
        public event EventHandler<NotifyCollectionChangedEventArgsEx<GameLocation>> CollectionChanged;
#endif

        #endregion

        [NonSerialized, XmlIgnore]
        protected DateTime _lastChanged = DateTime.Now;

        [NonSerialized, XmlIgnore]
        protected DateTime _lastSaved = DateTime.MinValue;

        public DateTime LastChanged { get { return _lastChanged; } set { _lastChanged = value; } }
        public DateTime LastSaved { get { return _lastSaved; } set { _lastSaved = value; } }
        public void MarkChanged() { _lastChanged = DateTime.Now; }
        public void MarkSaved() { _lastSaved = DateTime.Now; }
        public void MarkRestored() { MarkSaved(); }
    }

    public static class MapExtensions
    {
        [MethodVisible]
        public static GameLocation GameLocation(this LocationInt loc)
        {
            return WorldMap.Instance.World.FindItem(loc.X, loc.Y, loc.Z);
        }
    }
}

//      public void LoadAllGameThings()
//      {
//#if NO_SILVERLIGHT
//         AllGameThingsInWorld.ForEach((id) =>
//             {
//                string fileName = "C:\\gamething_" + id.ToString();
//                if (!File.Exists(fileName))
//                {
//                   // Remove from world?
//                }
//                else
//                {
//                   byte[] bytes = File.ReadAllBytes(fileName);
//                   IGameThing gameThing = bytes.DeserializeFromCompressedBinary() as IGameThing;
//                   Type t = gameThing.GetType();
//                   ICanSaveAndRestore saveAndRestore = gameThing as ICanSaveAndRestore;
//                   if (saveAndRestore != null)
//                   {
//                      saveAndRestore.PerformPostRestoreSetup();
//                   }
//                }
//             });
//#endif
//      }

//public void SaveAllGameThings()
//{
//   AllGameThingsInWorld.ForEach((id) =>
//       {
//          IGameThing gameThing = Everything.Find<IGameThing>(id);
//          ICanSaveAndRestore saveAndRestore = gameThing as ICanSaveAndRestore;
//          if (saveAndRestore != null)
//          {
//             // Use Save from the ICanSaveAndRestore interface
//             saveAndRestore.Save(null);
//          }
//       });
//}
